package com.sharp.portal.service;

import com.sharp.authorization.service.token.TokenService;
import com.sharp.kernel.utils.JsonUtil;
import com.sharp.portal.service.excel.PortalExcelService;
import com.sharp.portal.vo.FieldDisplayVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.File;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

/**
 * Title: PortalUploadService
 * Description: Copyright: Copyright (c) 2019 Company: BHFAE
 *
 * @author Sharp
 * @date 2020/11/13 15:51
 */
@Slf4j
public abstract class BasePortalUploadService {

    private static final String START = "0";
    private static final String FINISHED = "100";
    private static final String EXCEPTION = "-1";
    private static final String REMOVE = "";

    @Value("${my.project.name}")
    private String projectName;
    @Resource
    private TokenService tokenService;
    @Resource
    private PortalExcelService portalExcelService;
    @Resource
    private PortalQueryService portalQueryService;

    /**
     * 上传excel
     *
     * @param file 文件
     */
    public abstract void uploadExcel(File file);

    /**
     * 获取上传文件处理进度
     *
     * @return 处理进度
     */
    public abstract String getUploadProgress();

    @Async
    @Transactional(rollbackFor = Exception.class)
    public <ENTITY, SERVICE> void uploadExcel(File file, SERVICE service, LinkedHashMap<String, FieldDisplayVO> map,
                                              Class<ENTITY> entityClazz, String token) {
        String progressKey = buildProgressKey(entityClazz);
        tokenService.putItem(token, progressKey, START);
        List<LinkedHashMap<String, String>> excelMap = portalExcelService.readFromExcel(file, map);
        portalQueryService.convertLinkedHashMapDict(excelMap, map);
        List<ENTITY> dataList = JsonUtil.readJson(excelMap, List.class, entityClazz);
        insert(dataList, service, token, progressKey);
    }

    private <ENTITY> String buildProgressKey(Class<ENTITY> entityClazz) {
        return projectName + ":" + buildProgressKey() + ":" + entityClazz.getSimpleName();
    }

    private <ENTITY, SERVICE> void insert(List<ENTITY> dataList, SERVICE service, String token, String progressKey) {
        int result = 0;
        if (CollectionUtils.isEmpty(dataList)) {
            setUploadProgress(token, progressKey, FINISHED);
        } else {
            int page = dataList.size() / 100 + 1;
            page = Math.min(page, 100);
            BigDecimal total = new BigDecimal(String.valueOf(dataList.size()));

            int index = 0;
            List<ENTITY> tempList = new ArrayList<>();
            Set<Object> duplicateSet = new HashSet<>();
            for (ENTITY data : dataList) {
                if (isDuplicate(data, duplicateSet)) {
                    setUploadProgress(token, progressKey, ++result, total);
                } else {
                    tempList.add(data);
                    if (index++ == page) {
                        try {
                            result += insert(dataList, service);
                            index = 0;
                            tempList.clear();
                            duplicateSet.clear();
                        } catch (Exception e) {
                            setUploadProgress(token, progressKey, EXCEPTION);
                            throw e;
                        }
                        setUploadProgress(token, progressKey, result, total);
                    }
                }
            }
            if (CollectionUtils.isNotEmpty(tempList)) {
                try {
                    insert(tempList, service);
                    setUploadProgress(token, progressKey, FINISHED);
                } catch (Exception e) {
                    log.error("模板导入失败", e);
                    setUploadProgress(token, progressKey, EXCEPTION);
                }
            }
        }
        setUploadProgress(token, progressKey, FINISHED);
    }

    /**
     * 获取上传进度key
     *
     * @return 上传进度key
     */
    protected abstract String buildProgressKey();

    private void setUploadProgress(String token, String progressKey, String percent) {
        tokenService.putItem(token, progressKey, percent);
    }

    /**
     * 该数据是否需要插入
     *
     * @param data         数据
     * @param <ENTITY>     数据类型
     * @param duplicateSet 判断tempList中是否重复
     * @return 是否需要插入表内
     */
    protected abstract <ENTITY> boolean isDuplicate(ENTITY data, Set<Object> duplicateSet);

    private void setUploadProgress(String token, String progressKey, int result, BigDecimal total) {
        BigDecimal percent = new BigDecimal(String.valueOf(result * 100)).divide(total, 2, RoundingMode.CEILING);
        setUploadProgress(token, progressKey, percent.toString());
    }

    /**
     * 批量插入数据方法
     *
     * @param dataList  数据列表
     * @param service   服务
     * @param <ENTITY>  数据类型
     * @param <SERVICE> 服务类型
     * @return 插入次数
     */
    protected abstract <ENTITY, SERVICE> int insert(List<ENTITY> dataList, SERVICE service);

    @Async
    @Transactional(rollbackFor = Exception.class)
    public <ENTITY, SERVICE> void uploadExcel(List<ENTITY> dataList, SERVICE service, Class<ENTITY> entityClazz,
                                              String token) {
        String progressKey = buildProgressKey(entityClazz);
        tokenService.putItem(token, progressKey, START);
        insert(dataList, service, token, progressKey);
    }

    public <ENTITY> String getUploadProgress(Class<ENTITY> entityClazz, String token) {
        String progressKey = buildProgressKey(entityClazz);
        String res = tokenService.getItem(token, progressKey, String.class);
        if (StringUtils.equals(res, FINISHED)) {
            tokenService.putItem(token, progressKey, REMOVE);
        }
        return res;
    }
}
